/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the ohosannotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.helper;

import org.ohosannotations.ElementValidation;
import org.ohosannotations.OhosAnnotationsEnvironment;
import org.ohosannotations.annotations.ComponentById;
import org.ohosannotations.annotations.EAbility;
import org.ohosannotations.annotations.EBean;
import org.ohosannotations.annotations.EFraction;
import org.ohosannotations.annotations.EIntentService;
import org.ohosannotations.annotations.EReceiver;
import org.ohosannotations.annotations.EService;
import org.ohosannotations.annotations.EView;
import org.ohosannotations.annotations.EViewGroup;
import org.ohosannotations.annotations.Trace;
import org.ohosannotations.internal.core.model.OhosSystemServices;
import org.ohosannotations.internal.model.AnnotationElements;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;

import static java.util.Arrays.asList;
import static org.ohosannotations.helper.CanonicalNameConstants.INTERNET_PERMISSION;
import static org.ohosannotations.helper.CanonicalNameConstants.RUNNING_LOCK_PERMISSION;
import static org.ohosannotations.helper.ModelConstants.VALID_ENHANCED_COMPONENT_ANNOTATIONS;
import static org.ohosannotations.helper.ModelConstants.VALID_ENHANCED_VIEW_SUPPORT_ANNOTATIONS;
import static org.ohosannotations.helper.ModelConstants.classSuffix;
import static org.ohosannotations.helper.OhosConstants.LOG_DEBUG;
import static org.ohosannotations.helper.OhosConstants.LOG_ERROR;
import static org.ohosannotations.helper.OhosConstants.LOG_FATAL;
import static org.ohosannotations.helper.OhosConstants.LOG_INFO;
import static org.ohosannotations.helper.OhosConstants.LOG_WARN;

/**
 * 验证器辅助
 *
 * @author dev
 * @since 2021-07-22
 */
public class ValidatorHelper {
    private static final List<String> OHOS_FRACTION_QUALIFIED_NAMES = asList(CanonicalNameConstants.FRACTION);

    private static final Collection<Integer> VALID_LOG_LEVELS
        = asList(LOG_FATAL, LOG_DEBUG, LOG_INFO, LOG_WARN, LOG_ERROR);

    private static final List<String> VALID_PREFERENCE_CLASSES
        = asList(CanonicalNameConstants.PREFERENCE_ABILITY, CanonicalNameConstants.PREFERENCE_FRACTION,
        CanonicalNameConstants.SUPPORT_V4_PREFERENCE_FRACTION, CanonicalNameConstants
            .MACHINARIUS_V4_PREFERENCE_FRACTION, CanonicalNameConstants.SUPPORT_V7_PREFERENCE_FRACTIONCOMPAT,
        CanonicalNameConstants.SUPPORT_V14_PREFERENCE_FRACTION);

    private final ParcelerHelper parcelerHelper;
    /**
     * 注释的助手
     */
    protected final TargetAnnotationHelper annotationHelper;
    /**
     * 参数
     */
    public final ValidatorParameterHelper param;

    /**
     * ValidatorHelper
     *
     * @param targetAnnotationHelper targetAnnotationHelper
     */
    public ValidatorHelper(TargetAnnotationHelper targetAnnotationHelper) {
        annotationHelper = targetAnnotationHelper;
        param = new ValidatorParameterHelper(annotationHelper);
        parcelerHelper = new ParcelerHelper(environment());
    }

    /**
     * environment
     *
     * @return annotationHelper
     */
    protected OhosAnnotationsEnvironment environment() {
        return annotationHelper.getEnvironment();
    }

    /**
     * validatedModel
     *
     * @return environment
     */
    protected AnnotationElements validatedModel() {
        return environment().getValidatedElements();
    }

    /**
     * isNotFinal
     *
     * @param element element
     * @param valid element
     */
    public void isNotFinal(Element element, ElementValidation valid) {
        if (annotationHelper.isFinal(element)) {
            valid.addError("%s cannot be used on a final element");
        }
    }

    /**
     * isNotSynchronized
     *
     * @param element element
     * @param valid valid
     */
    public void isNotSynchronized(Element element, ElementValidation valid) {
        if (annotationHelper.isSynchronized(element)) {
            valid.addError("%s cannot be used on a synchronized element. " +
                "If you think you shall need to use the synchronized keyword " +
                "for a specific use case, please post on the mailing list.");
        }
    }

    /**
     * isInterface
     *
     * @param element element
     * @param valid valid
     */
    public void isInterface(TypeElement element, ElementValidation valid) {
        if (!annotationHelper.isInterface(element)) {
            valid.addError("%s can only be used on an interface");
        }
    }

    /**
     * isNotInterface
     *
     * @param element element
     * @param valid valid
     */
    public void isNotInterface(TypeElement element, ElementValidation valid) {
        if (annotationHelper.isInterface(element)) {
            valid.addError("%s cannot be used on an interface");
        }
    }

    /**
     * isTopLevel
     *
     * @param element element
     * @param valid valid
     */
    public void isTopLevel(TypeElement element, ElementValidation valid) {
        if (!annotationHelper.isTopLevel(element)) {
            valid.addError("%s can only be used on a top level type");
        }
    }

    /**
     * doesNotReturnPrimitive
     *
     * @param element element
     * @param valid valid
     */
    public void doesNotReturnPrimitive(ExecutableElement element, ElementValidation valid) {
        if (element.getReturnType().getKind().isPrimitive()) {
            valid.addError("%s cannot return primitive");
        }
    }

    /**
     * isNotPrivate
     *
     * @param element element
     * @param valid valid
     */
    public void isNotPrivate(Element element, ElementValidation valid) {
        if (annotationHelper.isPrivate(element)) {
            valid.addError("%s cannot be used on a private element");
        }
    }

    /**
     * isPublic
     *
     * @param element element
     * @param valid valid
     */
    public void isPublic(Element element, ElementValidation valid) {
        if (!annotationHelper.isPublic(element)) {
            valid.addError(element, "%s cannot be used on a non public element");
        }
    }

    /**
     * isStatic
     *
     * @param element element
     * @param valid valid
     */
    public void isStatic(Element element, ElementValidation valid) {
        if (!annotationHelper.isStatic(element)) {
            valid.addError(element, "%s cannot be used on a non static inner element");
        }
    }

    /**
     * enclosingElementIsNotAbstractIfNotAbstract
     *
     * @param element element
     * @param validation validation
     */
    public void enclosingElementIsNotAbstractIfNotAbstract(Element element,
        ElementValidation validation) {
        if (!annotationHelper.isAbstract(element) && annotationHelper
            .isAbstract(element.getEnclosingElement())) {
            validation.addError("%s cannot be used on a non-abstract " +
                "inner element whose outer element is abstract");
        }
    }

    /**
     * enclosingElementHasAnnotation
     *
     * @param annotation annotation
     * @param element element
     * @param validation validation
     */
    public void enclosingElementHasAnnotation(Class<? extends Annotation> annotation,
        Element element, ElementValidation validation) {
        enclosingElementHasOneOfAnnotations(element, Collections.<Class<? extends Annotation>>singletonList(annotation), validation);
    }

    /**
     * enclosingElementHasEBeanAnnotation
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasEBeanAnnotation(Element element, ElementValidation valid) {
        enclosingElementHasAnnotation(EBean.class, element, valid);
    }

    /**
     * enclosingElementHasEAbility
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasEAbility(Element element, ElementValidation valid) {
        enclosingElementHasAnnotation(EAbility.class, element, valid);
    }

    /**
     * enclosingElementHasEAbilityOrEFraction
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasEAbilityOrEFraction(Element element, ElementValidation valid) {
        List<Class<? extends Annotation>> validAnnotations = asList(EAbility.class, EFraction.class);
        enclosingElementHasOneOfAnnotations(element, validAnnotations, valid);
    }

    /**
     * enclosingElementHasEAbilityOrEFractionOrEViewOrEViewGroup
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasEAbilityOrEFractionOrEViewOrEViewGroup
    (Element element, ElementValidation valid) {
        List<Class<? extends Annotation>> validAnnotations
            = asList(EAbility.class, EFraction.class, EView.class, EViewGroup.class);
        enclosingElementHasOneOfAnnotations(element, validAnnotations, valid);
    }

    /**
     * EViewGroup
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHas
    (Element element, ElementValidation valid) {
        List<Class<? extends Annotation>> validAnnotations
            = asList(EAbility.class, EFraction.class, EService.class, EIntentService.class, EView.class, EViewGroup.class);
        enclosingElementHasOneOfAnnotations(element, validAnnotations, valid);
    }

    /**
     * enclosingElementHasEFraction
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasEFraction(Element element, ElementValidation valid) {
        enclosingElementHasAnnotation(EFraction.class, element, valid);
    }

    /**
     * enclosingElementHasEIntentService
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasEIntentService(Element element, ElementValidation valid) {
        enclosingElementHasAnnotation(EIntentService.class, element, valid);
    }

    /**
     * enclosingElementHasEReceiver
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasEReceiver(Element element, ElementValidation valid) {
        enclosingElementHasAnnotation(EReceiver.class, element, valid);
    }

    /**
     * hasEAbility
     *
     * @param element element
     * @param valid valid
     */
    public void hasEAbility(Element element, ElementValidation valid) {
        hasAnnotation(element, element, EAbility.class, valid);
    }

    /**
     * hasEAbilityOrEFraction
     *
     * @param element element
     * @param valid valid
     */
    public void hasEAbilityOrEFraction(Element element, ElementValidation valid) {
        List<Class<? extends Annotation>> validAnnotations = asList(EAbility.class, EFraction.class);
        hasOneOfAnnotations(element, element, validAnnotations, valid);
    }

    /**
     * SupportAnnotation
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasEnhancedViewSupportAnnotation(Element element, ElementValidation valid) {
        enclosingElementHasOneOfAnnotations(element, VALID_ENHANCED_VIEW_SUPPORT_ANNOTATIONS, valid);
    }

    /**
     * enclosingElementHasEnhancedComponentAnnotation
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasEnhancedComponentAnnotation(Element element, ElementValidation valid) {
        enclosingElementHasOneOfAnnotations(element, VALID_ENHANCED_COMPONENT_ANNOTATIONS, valid);
    }

    /**
     * enclosingElementHasOhosAnnotation
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementHasOhosAnnotation(Element element, ElementValidation valid) {
        enclosingElementHasOneOfAnnotations(element, environment().getGeneratingAnnotations(), valid);
    }

    /**
     * hasAnnotation
     *
     * @param element element
     * @param reportElement reportElement
     * @param validAnnotation validAnnotation
     * @param valid valid
     */
    private void hasAnnotation(Element element, Element reportElement,
        Class<? extends Annotation> validAnnotation, ElementValidation valid) {
        ArrayList<Class<? extends Annotation>> validAnnotations = new ArrayList<>();
        validAnnotations.add(validAnnotation);
        hasOneOfAnnotations(element, reportElement, validAnnotations, valid);
    }

    /**
     * enclosingElementHasOneOfAnnotations
     *
     * @param element element
     * @param validAnnotations validAnnotations
     * @param validation validation
     */
    public void enclosingElementHasOneOfAnnotations(Element element,
        List<Class<? extends Annotation>> validAnnotations, ElementValidation validation) {
        hasOneOfAnnotations(element, element.getEnclosingElement(), validAnnotations, validation);
    }

    /**
     * hasOneOfAnnotations
     *
     * @param reportElement reportElement
     * @param element element
     * @param validAnnotations validAnnotations
     * @param validation validation
     */
    public void hasOneOfAnnotations(Element reportElement, Element element,
        List<Class<? extends Annotation>> validAnnotations, ElementValidation validation) {
        checkAnnotations(reportElement, element, validAnnotations, true, validation);
    }

    /**
     * doesNotHaveOneOfAnnotations
     *
     * @param element element
     * @param validAnnotations validAnnotations
     * @param validation validation
     */
    public void doesNotHaveOneOfAnnotations(Element element,
        List<Class<? extends Annotation>> validAnnotations, ElementValidation validation) {
        checkAnnotations(element, element, validAnnotations, false, validation);
    }

    /**
     * doesNotHaveAnnotation
     *
     * @param element element
     * @param annotation annotation
     * @param validation validation
     */
    public void doesNotHaveAnnotation(Element element,
        Class<? extends Annotation> annotation, ElementValidation validation) {
        doesNotHaveOneOfAnnotations(element, Collections.<Class<? extends Annotation>>singletonList(annotation), validation);
    }

    /**
     * doesNotHaveAnyOfSupportedAnnotations
     *
     * @param element element
     * @param validation validation
     */
    public void doesNotHaveAnyOfSupportedAnnotations(Element element, ElementValidation validation) {
        Set<String> supportedAnnotationTypes = environment().getSupportedAnnotationTypes();
        for (AnnotationMirror annotationMirror : element.getAnnotationMirrors()) {
            if (supportedAnnotationTypes.contains(annotationMirror.getAnnotationType().toString())) {
                validation.addError(element, "method injection " +
                    "does only allow the annotation to be placed on the method OR on each parameter.");
                break;
            }
        }
    }

    /**
     * checkAnnotations
     *
     * @param reportElement reportElement
     * @param element element
     * @param validAnnotations validAnnotations
     * @param shouldFind shouldFind
     * @param validation validation
     */
    private void checkAnnotations(Element reportElement, Element element,
        List<Class<? extends Annotation>> validAnnotations, boolean shouldFind,
        ElementValidation validation) {
        boolean foundAnnotation = false;
        for (Class<? extends Annotation> validAnnotation : validAnnotations) {
            if (element.getAnnotation(validAnnotation) != null) {
                foundAnnotation = true;
                break;
            }
        }

        if (shouldFind != foundAnnotation) {
            String not = shouldFind ? "" : " not";

            validation.addError(reportElement,
                "%s can only be used in a " + element.getKind()
                    .toString().toLowerCase(Locale.ENGLISH) + not + " annotated with "
                    + getFormattedValidEnhancedBeanAnnotationTypes(validAnnotations) + ".");
        }
    }

    /**
     * getFormattedValidEnhancedBeanAnnotationTypes
     *
     * @param annotations annotations
     * @return toString
     */
    private String getFormattedValidEnhancedBeanAnnotationTypes(List<Class<? extends Annotation>> annotations) {
        StringBuilder sb = new StringBuilder();
        if (!annotations.isEmpty()) {
            sb.append("@" + annotations.get(0).getName());

            for (int i = 1; i < annotations.size(); i++) {
                sb.append(", ");
                sb.append("@" + annotations.get(i));
            }
        }

        return sb.toString();
    }

    /**
     * hasViewByIdAnnotation
     *
     * @param element element
     * @param valid valid
     */
    public void hasViewByIdAnnotation(Element element, ElementValidation valid) {
        String error = "can only be used with annotation";
        elementHasAnnotation(ComponentById.class, element, valid, error);
    }

    /**
     * enclosingElementHasAnnotation
     *
     * @param annotation annotation
     * @param element element
     * @param valid valid
     * @param error error
     */
    public void enclosingElementHasAnnotation
    (Class<? extends Annotation> annotation, Element element, ElementValidation valid, String error) {
        Element enclosingElement = element.getEnclosingElement();
        elementHasAnnotation(annotation, enclosingElement, valid, error);
    }

    /**
     * elementHasAnnotation
     *
     * @param annotation annotation
     * @param element element
     * @param valid valid
     * @param error error
     */
    public void elementHasAnnotation(Class<? extends Annotation> annotation,
        Element element, ElementValidation valid, String error) {
        if (!elementHasAnnotation(annotation, element)) {
            if (element.getAnnotation(annotation) == null) {
                valid.addError("%s " + error + " @" + annotation.getName());
            }
        }
    }

    /**
     * elementHasAnnotation
     *
     * @param annotation annotation
     * @param element element
     * @return layoutAnnotatedElements
     */
    public boolean elementHasAnnotation(Class<? extends Annotation> annotation, Element element) {
        Set<? extends Element> layoutAnnotatedElements
            = validatedModel().getRootAnnotatedElements(annotation.getName());
        return layoutAnnotatedElements.contains(element);
    }

    /**
     * typeHasAnnotation
     *
     * @param annotation annotation
     * @param element element
     * @param valid valid
     */
    public void typeHasAnnotation(Class<? extends Annotation> annotation,
        Element element, ElementValidation valid) {
        TypeMirror elementType = element.asType();
        typeHasAnnotation(annotation, elementType, valid);
    }

    /**
     * typeHasValidAnnotation
     *
     * @param annotation annotation
     * @param element element
     * @param valid valid
     */
    public void typeHasValidAnnotation(Class<? extends Annotation> annotation,
        Element element, ElementValidation valid) {
        typeHasAnnotation(annotation, element, valid);

        if (valid.isValid()) {
            typeIsValid(annotation, element.asType(), valid);
        }
    }

    /**
     * typeHasAnnotation
     *
     * @param annotation annotation
     * @param elementType elementType
     * @param valid valid
     */
    public void typeHasAnnotation(Class<? extends Annotation> annotation,
        TypeMirror elementType, ElementValidation valid) {
        Element typeElement = annotationHelper.getTypeUtils().asElement(elementType);
        if (!elementHasAnnotationSafe(annotation, typeElement)) {
            valid.addError("%s can only be used on an element annotated with @" + annotation.getName());
        }
    }

    /**
     * typeOrTargetValueHasAnnotation
     *
     * @param annotation annotation
     * @param element element
     * @param valid valid
     */
    public void typeOrTargetValueHasAnnotation(Class<? extends Annotation> annotation,
        Element element, ElementValidation valid) {
        Element targetElement = findTargetElement(element, valid);
        if (targetElement == null) {
            return;
        }

        DeclaredType targetAnnotationClassValue = annotationHelper
            .extractAnnotationClassParameter(element);

        if (targetAnnotationClassValue != null) {
            targetElement = targetAnnotationClassValue.asElement();

            if (!annotationHelper.getTypeUtils()
                .isAssignable(targetAnnotationClassValue, targetElement.asType())) {
                valid.addError("The value of %s must be assignable into the annotated field");
            }
        }

        typeHasValidAnnotation(annotation, targetElement, valid);
    }

    Element findTargetElement(Element element, ElementValidation valid) {
        if (element instanceof ExecutableElement) {
            ExecutableElement executableElement = (ExecutableElement) element;
            returnTypeIsVoid(executableElement, valid);
            if (!valid.isValid()) {
                return null;
            }

            List<? extends VariableElement> parameters = executableElement.getParameters();
            if (parameters.size() != 1) {
                valid.addError("The method can only have 1 parameter");
                return null;
            }
            return parameters.get(0);
        }
        return element;
    }

    /**
     * typeIsValid
     *
     * @param annotation annotation
     * @param elementType elementType
     * @param elementValidation elementValidation
     */
    public void typeIsValid(Class<? extends Annotation> annotation,
        TypeMirror elementType, ElementValidation elementValidation) {
        Set<? extends Element> validElements = validatedModel()
            .getRootAnnotatedElements(annotation.getName());

        Set<? extends Element> extractedElements = environment()
            .getExtractedElements().getRootAnnotatedElements(annotation.getName());

        Element typeElement = annotationHelper.getTypeUtils().asElement(elementType);

        if (!extractedElements.contains(typeElement) || validElements.contains(typeElement)) {
            return;
        }

        elementValidation.addError("The type " + typeElement.getSimpleName()
            + " is invalid, " + "please check the messages on that type.");
    }

    /**
     * elementHasAnnotationSafe
     *
     * @param annotation annotation
     * @param element element
     * @return false
     */
    private boolean elementHasAnnotationSafe(Class<? extends Annotation> annotation, Element element) {
        List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
        for (AnnotationMirror annotationMirror : annotationMirrors) {
            if (annotationMirror.getAnnotationType().toString().equals(annotation.getName())) {
                return true;
            }
        }
        return false;
    }

    /**
     * numberOfElementParameterHasAnnotation
     *
     * @param element element
     * @param annotation annotation
     * @return count
     */
    public int numberOfElementParameterHasAnnotation(ExecutableElement element,
        Class<? extends Annotation> annotation) {
        int count = 0;
        for (VariableElement parameter : element.getParameters()) {
            if (parameter.getAnnotation(annotation) != null) {
                count++;
            }
        }
        return count;
    }

    /**
     * doesntThrowException
     *
     * @param element element
     * @param valid valid
     */
    public void doesntThrowException(Element element, ElementValidation valid) {
        ExecutableElement executableElement = (ExecutableElement) element;
        if (executableElement.getThrownTypes().size() > 0) {
            valid.addError("%s annotated methods should not declare throwing any exception");
        }
    }

    /**
     * returnTypeIsVoidOrBoolean
     *
     * @param executableElement executableElement
     * @param valid valid
     */
    public void returnTypeIsVoidOrBoolean(ExecutableElement executableElement, ElementValidation valid) {
        TypeMirror returnType = executableElement.getReturnType();
        TypeKind returnKind = returnType.getKind();
        if (returnKind != TypeKind.BOOLEAN && returnKind
            != TypeKind.VOID && !returnType.toString().equals(CanonicalNameConstants.BOOLEAN)) {
            valid.addError("%s can only be used on a method with a boolean or a void return type");
        }
    }

    /**
     * returnTypeIsVoid
     *
     * @param executableElement executableElement
     * @param valid valid
     */
    public void returnTypeIsVoid(ExecutableElement executableElement, ElementValidation valid) {
        TypeMirror returnType = executableElement.getReturnType();
        if (returnType.getKind() != TypeKind.VOID) {
            valid.addError("%s can only be used on a method with a void return type");
        }
    }

    /**
     * returnTypeIsNotVoid
     *
     * @param executableElement executableElement
     * @param valid valid
     */
    public void returnTypeIsNotVoid(ExecutableElement executableElement, ElementValidation valid) {
        TypeMirror returnType = executableElement.getReturnType();
        if (returnType.getKind() == TypeKind.VOID) {
            valid.addError("%s can only be used on a method with a return type non void");
        }
    }

    /**
     * extendsAbility
     *
     * @param element element
     * @param valid valid
     */
    public void extendsAbility(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.ABILITY, valid);
    }

    /**
     * extendsFraction
     *
     * @param element element
     * @param valid valid
     */
    public void extendsFraction(Element element, ElementValidation valid) {
        extendsOneOfTypes(element, OHOS_FRACTION_QUALIFIED_NAMES, valid);
    }

    /**
     * extendsService
     *
     * @param element element
     * @param valid valid
     */
    public void extendsService(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.SERVICE, valid);
    }

    /**
     * extendsIntentService
     *
     * @param element element
     * @param valid valid
     */
    public void extendsIntentService(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.INTENT_SERVICE, valid);
    }

    /**
     * extendsReceiver
     *
     * @param element element
     * @param valid valid
     */
    public void extendsReceiver(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.BROADCAST_RECEIVER, valid);
    }

    /**
     * extendsProvider
     *
     * @param element element
     * @param valid valid
     */
    public void extendsProvider(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.CONTENT_PROVIDER, valid);
    }

    /**
     * extendsView
     *
     * @param element element
     * @param valid valid
     */
    public void extendsView(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.COMPONENT, valid);
    }

    /**
     * extendsTextView
     *
     * @param element element
     * @param valid valid
     */
    public void extendsTextView(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.TEXT_VIEW, valid);
    }

    /**
     * extendsViewGroup
     *
     * @param element element
     * @param valid valid
     */
    public void extendsViewGroup(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.COMPONENT_CONTAINER, valid);
    }

    /**
     * extendsApplication
     *
     * @param element element
     * @param valid valid
     */
    public void extendsApplication(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.APPLICATION, valid);
    }

    /**
     * extendsContext
     *
     * @param element element
     * @param valid valid
     */
    public void extendsContext(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.CONTEXT, valid);
    }

    /**
     * extendsMenuItem
     *
     * @param element element
     * @param valid valid
     */
    public void extendsMenuItem(Element element, ElementValidation valid) {
        Element enclosingElement = element.getEnclosingElement();
        String enclosingQualifiedName = enclosingElement.asType().toString();
        TypeElement enclosingTypeElement = annotationHelper
            .typeElementFromQualifiedName(enclosingQualifiedName);

        if (enclosingTypeElement != null) {
            extendsType(element, CanonicalNameConstants.MENU_ITEM, valid);
        }
    }

    /**
     * extendsMenu
     *
     * @param element element
     * @param validation validation
     */
    public void extendsMenu(Element element, ElementValidation validation) {
        Element enclosingElement = element.getEnclosingElement();
        String enclosingQualifiedName = enclosingElement.asType().toString();
        TypeElement enclosingTypeElement = annotationHelper
            .typeElementFromQualifiedName(enclosingQualifiedName);

        if (enclosingTypeElement != null) {
            extendsType(element, CanonicalNameConstants.MENU, validation);
        }
    }

    /**
     * extendsListOfView
     *
     * @param element element
     * @param valid valid
     */
    public void extendsListOfView(Element element, ElementValidation valid) {
        DeclaredType elementType = (DeclaredType) element.asType();
        List<? extends TypeMirror> elementTypeArguments = elementType.getTypeArguments();

        TypeMirror viewType = annotationHelper
            .typeElementFromQualifiedName(CanonicalNameConstants.COMPONENT).asType();

        if (!elementType.toString().equals(CanonicalNameConstants.LIST)
            && elementTypeArguments.size() == 1
            && !annotationHelper.isSubtype(elementTypeArguments.get(0), viewType)) {
            valid.invalidate();
            valid.addError("%s can only be used on a " + CanonicalNameConstants.LIST
                + " of elements extending " + CanonicalNameConstants.COMPONENT);
        }
    }

    /**
     * extendsPreference
     *
     * @param element element
     * @param validation validation
     */
    public void extendsPreference(Element element, ElementValidation validation) {
        extendsOneOfTypes(element, asList(CanonicalNameConstants
            .PREFERENCE, CanonicalNameConstants.SUPPORT_V7_PREFERENCE), validation);
    }

    /**
     * extendsOneOfTypes
     *
     * @param element element
     * @param typeQualifiedNames typeQualifiedNames
     * @param valid valid
     */
    public void extendsOneOfTypes(Element element,
        List<String> typeQualifiedNames, ElementValidation valid) {
        TypeMirror elementType = element.asType();

        for (String typeQualifiedName : typeQualifiedNames) {
            TypeElement typeElement = annotationHelper
                .typeElementFromQualifiedName(typeQualifiedName);
            if (typeElement != null) {
                TypeMirror expectedType = typeElement.asType();
                if (annotationHelper.isSubtype(elementType, expectedType)) {
                    return;
                }
            }
        }
        valid.addError("%s can only be used on an element that " +
            "extends one of the following classes: " + typeQualifiedNames);
    }

    /**
     * extendsType
     *
     * @param element element
     * @param typeQualifiedName typeQualifiedName
     * @param valid valid
     */
    public void extendsType(Element element, String typeQualifiedName, ElementValidation valid) {
        if (!extendsType(element, typeQualifiedName)) {
            valid.addError("%s can only be used on an element that extends " + typeQualifiedName);
        }
    }

    /**
     * extendsType
     *
     * @param element element
     * @param typeQualifiedName typeQualifiedName
     * @return false
     */
    protected boolean extendsType(Element element, String typeQualifiedName) {
        TypeMirror elementType = element.asType();

        TypeElement typeElement = annotationHelper.typeElementFromQualifiedName(typeQualifiedName);
        if (typeElement != null) {
            TypeMirror expectedType = typeElement.asType();
            return annotationHelper.isSubtype(elementType, expectedType);
        }
        return false;
    }

    /**
     * allowedType
     *
     * @param element element
     * @param allowedTypes allowedTypes
     * @param valid valid
     */
    public void allowedType(Element element, List<String> allowedTypes, ElementValidation valid) {
        String qualifiedName;
        Element enclosingElement = element.getEnclosingElement();
        if (element instanceof VariableElement && enclosingElement instanceof ExecutableElement) {
            qualifiedName = element.asType().toString();
        } else if (element instanceof ExecutableElement) {
            element = ((ExecutableElement) element).getParameters().get(0);
            qualifiedName = element.asType().toString();
        } else {
            qualifiedName = element.asType().toString();
        }

        if (!allowedTypes.contains(qualifiedName)) {
            valid.addError("%s can only be used on a field which is a "
                + allowedTypes.toString() + ", not " + qualifiedName);
        }
    }

    /**
     * ohosService
     *
     * @param element element
     * @param valid valid
     */
    public void ohosService(Element element, ElementValidation valid) {
        Element targetElement = findTargetElement(element, valid);
        if (targetElement == null) {
            return;
        }

        OhosSystemServices ohosSystemServices = new OhosSystemServices(environment());
        TypeMirror serviceType = targetElement.asType();
        if (!ohosSystemServices.contains(serviceType)) {
            valid.addError("Unknown service type: " + serviceType.toString());
        }
    }

    /**
     * isDeclaredType
     *
     * @param element element
     * @param valid valid
     */
    public void isDeclaredType(Element element, ElementValidation valid) {
        if (!(element.asType() instanceof DeclaredType)) {
            valid.addError("%s can only be used on a field which is a declared type");
        }
    }

    /**
     * notAlreadyValidated
     *
     * @param element element
     * @param valid valid
     */
    public void notAlreadyValidated(Element element, ElementValidation valid) {
        if (validatedModel().getAllElements().contains(element)) {
            valid.addError("%s annotated element cannot be used with "
                + "the other annotations used on this element.");
        }
    }

    /**
     * Constructor
     *
     * @param element element
     * @param valid valid
     */
    public void isAbstractOrHasEmptyOrContextConstructor(Element element, ElementValidation valid) {
        List<ExecutableElement> constructors = ElementFilter.constructorsIn(element.getEnclosedElements());

        if (!annotationHelper.isAbstract(element)) {
            if (constructors.size() == 1) {
                ExecutableElement constructor = constructors.get(0);

                if (!annotationHelper.isPrivate(constructor)) {
                    if (constructor.getParameters().size() > 1) {
                        valid.addError("%s annotated element should have a " +
                            "constructor with one parameter max, of type "
                            + CanonicalNameConstants.CONTEXT);
                    } else if (constructor.getParameters().size() == 1) {
                        VariableElement parameter = constructor.getParameters().get(0);
                        if (!parameter.asType().toString().equals(CanonicalNameConstants.CONTEXT)) {
                            valid.addError("%s annotated element should have a " +
                                "constructor with one parameter max, of type "
                                + CanonicalNameConstants.CONTEXT);
                        }
                    }
                } else {
                    valid.addError("%s annotated element should not have a private constructor");
                }
            } else {
                valid.addError("%s annotated element should have only one constructor");
            }
        }
    }

    /**
     * isAbstractOrHasEmptyConstructor
     *
     * @param element element
     * @param valid valid
     */
    public void isAbstractOrHasEmptyConstructor(Element element, ElementValidation valid) {
        List<ExecutableElement> constructors = ElementFilter.constructorsIn(element.getEnclosedElements());

        if (!annotationHelper.isAbstract(element)) {
            if (constructors.size() == 1) {
                ExecutableElement constructor = constructors.get(0);

                if (!annotationHelper.isPrivate(constructor)) {
                    if (constructor.getParameters().size() != 0) {
                        valid.addError("%s annotated element should have an empty constructor");
                    }
                } else {
                    valid.addError("%s annotated element should not have a private constructor");
                }
            } else {
                valid.addError("%s annotated element should have only one constructor");
            }
        }
    }

    /**
     * hasValidLogLevel
     *
     * @param element element
     * @param valid valid
     */
    public void hasValidLogLevel(Element element, ElementValidation valid) {
        Trace annotation = element.getAnnotation(Trace.class);
        Integer level = annotation.level();

        if (!VALID_LOG_LEVELS.contains(level)) {
            valid.addError("Unrecognized log level.");
        }
    }

    /**
     * canBePutInABundle
     *
     * @param element element
     * @param valid valid
     */
    public void canBePutInABundle(Element element, ElementValidation valid) {
        TypeMirror typeMirror = element.asType();
        String typeString = element.asType().toString();

        if (!isKnownBundleCompatibleType(typeString)) {

            if (typeMirror instanceof ArrayType) {
                ArrayType arrayType = (ArrayType) element.asType();
                typeMirror = arrayType.getComponentType();
            }

            if (typeMirror.getKind() != TypeKind.NONE) {
                TypeElement typeElement = annotationHelper
                    .typeElementFromQualifiedName(CanonicalNameConstants.SEQUENCEABLE);
                TypeMirror parcelableType = typeElement.asType();
                TypeMirror serializableType = annotationHelper
                    .typeElementFromQualifiedName("java.io.Serializable").asType();

                if (typeString.startsWith(CanonicalNameConstants.SPARSE_ARRAY)) {
                    DeclaredType declaredType = (DeclaredType) typeMirror;
                    List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
                    if (typeArguments.size() != 1 || !annotationHelper
                        .isSubtype(typeArguments.get(0), parcelableType)) {
                        valid.addError("Unrecognized type. The type argument "
                            + "of SparseArray should implement Parcelable.");
                    }
                } else if (!annotationHelper.isSubtype(typeMirror, parcelableType)
                    && !annotationHelper.isSubtype(typeMirror, serializableType)
                    && !parcelerHelper.isParcelType(typeMirror)) {
                    valid.addError("Unrecognized type. Please let your attribute "
                        + "be primitive or implement Serializable or Parcelable or an annotated Parceler bean.");
                } else {
                }
            }
        }
    }

    /**
     * isKnownBundleCompatibleType
     *
     * @param type type
     * @return BundleHelper
     */
    private boolean isKnownBundleCompatibleType(String type) {
        return BundleHelper.METHOD_SUFFIX_BY_TYPE_NAME.containsKey(type);
    }

    /**
     * componentRegistered
     *
     * @param element element
     * @param ohosManifest ohosManifest
     * @param valid valid
     */
    public void componentRegistered(Element element,
        OhosManifest ohosManifest, ElementValidation valid) {
        componentRegistered(element, ohosManifest, true, valid);
    }

    /**
     * componentRegistered
     *
     * @param element element
     * @param ohosManifest ohosManifest
     * @param printWarning printWarning
     * @param valid valid
     */
    public void componentRegistered(Element element, OhosManifest ohosManifest,
        boolean printWarning, ElementValidation valid) {
        TypeElement typeElement = (TypeElement) element;

        if (typeElement.getModifiers().contains(Modifier.ABSTRACT)) {
            return;
        }

        if (ohosManifest.isLibraryProject()) {
            return;
        }

        String componentQualifiedName = typeElement.getQualifiedName().toString();
        String generatedComponentQualifiedName = componentQualifiedName + classSuffix();

        List<String> componentQualifiedNames = ohosManifest.getComponentQualifiedNames();
        if (!componentQualifiedNames.contains(generatedComponentQualifiedName)) {
            String simpleName = typeElement.getSimpleName().toString();
            String generatedSimpleName = simpleName + classSuffix();
            if (componentQualifiedNames.contains(componentQualifiedName)) {
                valid.addError("The ohosManifest file contains the original component, "
                    + "and not the ohosannotations generated component. Please register "
                    + generatedSimpleName
                    + " instead of " + simpleName);
            } else {
                if (printWarning) {
                    valid.addWarning("The component " + generatedSimpleName
                        + " is not registered in the ohosManifest.xml file.");
                }
            }
        }
    }

    /**
     * isDebuggable
     *
     * @param ohosManifest ohosManifest
     * @param valid valid
     */
    public void isDebuggable(OhosManifest ohosManifest, ElementValidation valid) {
        if (!ohosManifest.isDebuggable()) {
            valid.addError("The application must be in debuggable mode"
                + ". Please set ohos:debuggable to true in your ohosManifest.xml file.");
        }
    }

    /**
     * hasInternetPermission
     *
     * @param ohosManifest ohosManifest
     * @param valid valid
     */
    public void hasInternetPermission(OhosManifest ohosManifest, ElementValidation valid) {
        hasPermission(ohosManifest, valid, INTERNET_PERMISSION);
    }

    /**
     * hasWakeLockPermission
     *
     * @param ohosManifest ohosManifest
     * @param valid valid
     */
    public void hasWakeLockPermission(OhosManifest ohosManifest, ElementValidation valid) {
        hasPermission(ohosManifest, valid, RUNNING_LOCK_PERMISSION);
    }

    /**
     * hasPermission
     *
     * @param ohosManifest ohosManifest
     * @param valid valid
     * @param permissionQualifiedName permissionQualifiedName
     */
    public void hasPermission(OhosManifest ohosManifest,
        ElementValidation valid, String permissionQualifiedName) {
        List<String> permissionQualifiedNames = ohosManifest.getPermissionQualifiedNames();
        if (!permissionQualifiedNames.contains(permissionQualifiedName)) {
            if (ohosManifest.isLibraryProject()) {
                valid.addWarning("Your library should require the "
                    + permissionQualifiedName + " permission.");
            } else {
                valid.addError("Your application must require the "
                    + permissionQualifiedName + " permission.");
            }
        }
    }

    /**
     * SameName
     *
     * @param element element
     * @param valid valid
     * @param annotation annotation
     */
    public void hasNotMultipleAnnotatedMethodWithSameName(Element element,
        ElementValidation valid, Class<? extends Annotation> annotation) {
        Set<String> actionNames = new TreeSet<>();

        List<? extends Element> enclosedElements = element.getEnclosedElements();
        for (Element enclosedElement : enclosedElements) {
            if (enclosedElement.getKind() != ElementKind.METHOD
                || !annotationHelper.hasOneOfClassAnnotations(enclosedElement, annotation)) {
                continue;
            }

            String enclosedElementName = enclosedElement.getSimpleName().toString();
            if (actionNames.contains(enclosedElementName)) {
                valid.addError(enclosedElement, "The "
                    + TargetAnnotationHelper.annotationName(annotation)
                    + " annotated method must have unique name even if the signature is not the same");
            } else {
                actionNames.add(enclosedElementName);
            }
        }
    }

    /**
     * Fraction
     *
     * @param element element
     * @param valid valid
     */
    public void extendsPreferenceAbilityOrPreferenceFraction(Element element, ElementValidation valid) {
        extendsOneOfTypes(element, VALID_PREFERENCE_CLASSES, valid);
    }

    /**
     * Ability
     *
     * @param element element
     * @param valid valid
     */
    public void extendsPreferenceAbility(Element element, ElementValidation valid) {
        extendsType(element, CanonicalNameConstants.PREFERENCE_ABILITY, valid);
    }

    /**
     * PreferenceFraction
     *
     * @param element element
     * @param valid valid
     */
    public void enclosingElementExtendsPreferenceAbilityOrPreferenceFraction
    (Element element, ElementValidation valid) {
        extendsOneOfTypes(element.getEnclosingElement(), VALID_PREFERENCE_CLASSES, valid);
    }

    /**
     * isClassPresent
     *
     * @param className className
     * @return annotationHelper
     */
    public boolean isClassPresent(String className) {
        return annotationHelper.getElementUtils().getTypeElement(className) != null;
    }

    /**
     * ClassPresent
     *
     * @param element element
     * @param valid valid
     */
    public void isPreferenceFractionClassPresent(Element element, ElementValidation valid) {
        if (!isClassPresent(CanonicalNameConstants.PREFERENCE_FRACTION)) {
            valid.addError("The class "
                + CanonicalNameConstants.PREFERENCE_FRACTION
                + " cannot be found. You have to use at least API 11");
        }
    }

    /**
     * isViewPagerClassPresent
     *
     * @param validation validation
     */
    public void isViewPagerClassPresent(ElementValidation validation) {
        if (!isClassPresent(CanonicalNameConstants.VIEW_PAGER)) {
            validation.addError("The classes " + CanonicalNameConstants.VIEW_PAGER
                + " cannot be found. You have to include support-v4 or viewpager library");
        }
    }
}
